package com.ly.spring;

import com.ly.spring.biz.UserBiz;
import lombok.Data;


import org.springframework.beans.factory.NoSuchBeanDefinitionException;
import org.springframework.context.annotation.Scope;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Lazy;
import org.springframework.context.annotation.Primary;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Controller;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;

import java.io.File;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class MyAnnotationConfigApplicationContext {
    Class configClass;
    Object configObject;

    Map<String, BeanDefinition> beanDefinitionMap = new HashMap<>();

    Map<String, Object> singtonObjects = new HashMap<>();

    public MyAnnotationConfigApplicationContext(Class configClass) throws Exception {
        this.configClass = configClass;
        configObject = configClass.newInstance();



        //读取配置
        addBeanDefinitionFromMethod();
        addBeanDefinitionFromPackage();
        beanDefinitionMap.forEach((id,bean) ->{
            System.out.printf("%s: %s \n ", id,bean);
        });


        // 根据 BeanDefinition 创建 Bean, 存入 IOC singtonObjects
        createBean();

        // 后续实现 getBean

        // 实现自动装载 autowired

    }
    public Object getBean(String id) throws Exception {
        Object bean = singtonObjects.get(id);
        if (bean == null) {
            throw new RuntimeException("没有该bean："+ id);

        }

        return bean;
    }

    public <T> T getBean( Class<T> tClass ) {
        List<Map.Entry> list = singtonObjects.entrySet().stream().filter(entry->
                entry.getValue().getClass().isAssignableFrom(tClass))
                .collect(Collectors.toList());
        if(list.isEmpty()){
            throw new RuntimeException("没有该bean"+tClass);
        } else if (list.size() == 1) {
            return (T) list.get(0).getValue();
        }else{
            List<Map.Entry> list1 = list.stream()
                    .filter(entry->{
                    BeanDefinition bd = beanDefinitionMap.get(entry.getKey());
                    return bd.isPrimary();
                    })
                    .collect(Collectors.toList());
            if(list1.isEmpty()){
                throw new RuntimeException("该bean没有定义primary bean"+tClass);
            }else if(list1.size() == 1){
                return (T) list1.get(0).getValue();
            }else {
                throw new RuntimeException("该bean定义了多个primary bean"+tClass);

            }
        }

    }

    private  void createBean() throws Exception {
        for (Map.Entry<String, BeanDefinition> entry : beanDefinitionMap.entrySet()) {
            String id = entry.getKey();
            BeanDefinition bd = entry.getValue();

            if(bd.beanMethod != null){
                //仅执行五参数的 bean方法
                if (bd.beanMethod.getParameterCount() == 0){
                    bd.beanMethod.setAccessible(true);
                    Object bean = bd.beanMethod.invoke(configObject);
                    singtonObjects.put(id,bean);
                }
            }else {
                Object bean = bd.beanClass.newInstance();
                singtonObjects.put(id,bean);
            }
        }
    }



    private void addBeanDefinitionFromPackage() throws Exception {
        ComponentScan componentScan = (ComponentScan) configClass.getAnnotation(ComponentScan.class);
        if (componentScan != null) {
            String[] packages = componentScan.value();
            URL url = configClass.getClassLoader().getResource("");
            //编译根路径
            String classesPath = URLDecoder.decode(url.getPath()).substring(1);

            //扫描根路径
            String scanRootPath;
            List<File> classFileList = new ArrayList<>();
            File classesFile = new File(classesPath);
            for (String aPackage : packages) {
                System.out.println("aPackage = " + aPackage);
                scanRootPath = classesPath + aPackage.replaceAll("\\.","/");
                scanClassFile(new File(scanRootPath), classFileList);

            }
            for (File classFile : classFileList) {
               String classPath = classFile.getAbsolutePath()
                       .replace(classesFile.getAbsolutePath(), "");
               classPath = classPath.replace(".class","");
               classPath = classPath.substring(1);
               classPath = classPath.replaceAll("\\\\",".");
               System.out.println("classPath = " + classPath);
               Class beanClass = Class.forName(classPath);

               Service service = (Service) beanClass.getAnnotation(Service.class);
               Repository repository = (Repository) beanClass.getAnnotation(Repository.class);
               Controller controller = (Controller) beanClass.getAnnotation(Controller.class);
               Component component = (Component) beanClass.getAnnotation(Component.class);

               if (service == null && repository == null &&
                       controller == null && component == null) {
                   continue;
               }

               //构建 BeanDefinition
               BeanDefinition bd = new BeanDefinition();
               bd.beanClass = beanClass;
               bd.lazy = beanClass.getAnnotation(Primary.class) != null;
               bd.primary = beanClass.getAnnotation(Primary.class) != null;

               if (beanClass.getAnnotation(Scope.class) != null) {
                    Scope scopeAnno = (Scope)beanClass.getAnnotation(Scope.class);
                    bd.scope = scopeAnno.value(); // 使用方法获取值
               }

               if(service != null && service.value().isBlank() == false){
                   bd.id = service.value();
               } else if (repository != null && repository.value().isBlank() == false) {
                   bd.id = repository.value();
               }else if (controller != null && controller.value().isBlank() == false) {
                   bd.id = controller.value();
               }else if (component != null && component.value().isBlank() == false) {
                   bd.id = component.value();
               }else{
                   String id = beanClass.getSimpleName();
                   id = id.substring(0, 1).toLowerCase() + id.substring(1);
                   bd.id = id;
               }
                beanDefinitionMap.put(bd.id, bd);

            }
        }
    }
    private void scanClassFile(File dir, List<File> classFileList) {
        File[] classFiles = dir.listFiles(file -> file.getName().endsWith(".class"));
        for (File classFile : classFiles) {
            classFileList.add(classFile);
        }

        File[] subDirs = dir.listFiles(file -> file.isDirectory());
        for (File subDir : subDirs) {
            scanClassFile(subDir, classFileList);
        }

    }


    private void addBeanDefinitionFromMethod() {
        Method[] declaredMethods = configClass.getDeclaredMethods();
        for (Method method : declaredMethods) {
            if (method.getAnnotation(Bean.class) != null) {
                BeanDefinition bd = new BeanDefinition();

                bd.beanMethod = method;
                bd.beanClass = method.getReturnType();
                bd.lazy = method.getAnnotation(Lazy.class) != null;
                bd.primary = method.getAnnotation(Primary.class) != null;
                Bean beanAnno = method.getAnnotation(Bean.class);

                if (beanAnno.value().length>0) {
                    bd.id = beanAnno.value()[0];
                }else {
                    bd.id = method.getName();
                }

                if (method.getAnnotation(Scope.class) != null) {
                    Scope scopeAnno = method.getAnnotation(Scope.class);
                    bd.scope = scopeAnno.value(); // 使用方法获取值
                }

                beanDefinitionMap.put(bd.id, bd);

            }
        }
    }
    public static void main(String[] args) throws Exception {
        MyAnnotationConfigApplicationContext cxt;
        cxt = new MyAnnotationConfigApplicationContext(AnnoConfig.class);

        System.out.println("---------------------------");
        Object userMapper = cxt.getBean("userMapper");
        System.out.println("userMapper = " + userMapper);

        UserBiz userBiz = cxt.getBean(UserBiz.class);
        System.out.println("userBiz = " + userBiz);

        cxt.getBean("userMappersss");

    }
}

@Data
class BeanDefinition{
    String id;

    Class beanClass;

    Method beanMethod;

    boolean lazy;
    String scope;

    boolean primary;

}
